#!/bin/sh
#
# Copyright (c) 2012-2017, DilOS.
#
# Permission is hereby granted, free of charge, to any person obtaining a  copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the  rights
# to use, copy, modify, merge, publish,  distribute,  sublicense,  and/or  sell
# copies of the Software, and  to  permit  persons  to  whom  the  Software  is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall  be  included  in
# all copies or substantial portions of the Software.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
# Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
# Copyright 2017 RackTop Systems.
# Copyright 2017 DilOS
#

# 0a  Initialization.

[ -f /lib/svc/share/smf_include.sh ] || exit 1

. /lib/svc/share/smf_include.sh

activity=false

EMI_SERVICE="svc:/system/early-manifest-import:default"
PROFILE_DIR_SITE="/etc/svc/profile/site"

X=
ALT_REPOSITORY=
ALT_MFST_DIR=
early=false
[ "$SMF_FMRI" = "$EMI_SERVICE" ] && early=true
AWK=/usr/bin/gawk

usage()
{
	echo "Usage: /lib/svc/method/manifest-import [-n]" \
	    "[-f repository-file -d manifest-directory]"
	echo -e "\nOptions:"
	echo "-n dryrun"
	echo -e "-f and -d specify alternate repository and" \
	    "manifest directory for import\n"
	exit 2
}

while getopts "nd:f:" opt; do
	case $opt in
		n)	X=echo;;
		d)	ALT_MFST_DIR=$OPTARG;;
		f)	ALT_REPOSITORY=$OPTARG;;
		?)	usage;;
	esac
done

#
# Both -f and -d options must be specified together or not specified at all
#
[ -n "$ALT_REPOSITORY" ] && [ -z "$ALT_MFST_DIR" ] && usage
[ -n "$ALT_MFST_DIR" ] && [ -z "$ALT_REPOSITORY" ] && usage

svccfg_apply() {
	$X /usr/sbin/svccfg apply $1
	if [ $? -ne 0 ]; then
		echo "WARNING: svccfg apply $1 failed" | tee /dev/msglog
	fi
}

#
# If the smf repository has file entries that are missing
# then there is work to be done by the cleanup process.
#
cleanup_needwork()
{
	smfmfiles=`svcprop -p manifestfiles '*' 2>/dev/null |
	    $AWK -v early="$early" '$2 == "astring" &&
	    (early != "true" || $3 ~ "^/lib/") { print $3 }'`

	nw=`/lib/svc/bin/mfstscan $smfmfiles 2>&1 1>/dev/null`
	[ "$nw" ] && return 1

	return 0
}

#
# Upon upgrading to early manifest import code, preserve hashes of system
# profiles which lived under /var/svc/profile so that svccfg apply would
# not re-apply the profiles and overwrite user customizations. Simply 
# migrate manifestfile and hash values to new property groups named after
# profiles under /etc/svc/profile. If the profiles don't really exist,
# svccfg cleanup will remove the property groups in a later step.
# 
# Existing generic.xml, inetd_services.xml, and name_service.xml symlinks
# need to be preserved.
#
# Don't process site.xml profile since it is still supported under
# /var/svc/profile directory.
#
preserve_system_profiles()
{

	#
	# If /var is a separate fs, return and let Late Import
	# preserves the hashes.
	#	
	[ -d "/var/svc/profile" ] || return 1

	#
	# Preserve hashes for the following profiles: generic (two
	# cases) and platform (uname -i, uname -m outputs).
	#
	gn="var_svc_profile_generic_open_xml"
	gh=`/usr/bin/svcprop -p ${gn}/md5sum smf/manifest 2>/dev/null`
	[ $? = 0 ] || gh=""
	gn="etc_svc_profile_generic_open_xml"

	gln="var_svc_profile_generic_limited_net_xml"
	glh=`/usr/bin/svcprop -p ${gln}/md5sum smf/manifest 2>/dev/null`
	[ $? = 0 ] || glh=""
	gln="etc_svc_profile_generic_limited_net_xml"

	LC_ALL=C pl=`/usr/bin/uname -i | /usr/bin/tr , _`
	pln="var_svc_profile_platform_${pl}_xml"
	plh=`/usr/bin/svcprop -p ${pln}/md5sum smf/manifest 2>/dev/null`
	[ $? = 0 ] || plh=""
	pln="etc_svc_profile_platform_${pl}_xml"

	LC_ALL=C plm=`/usr/bin/uname -m | /usr/bin/tr , _`
	if [ $plm != $pl ]; then
		plmn="var_svc_profile_platform_${plm}_xml"
		plmh=`/usr/bin/svcprop -p ${plmn}/md5sum smf/manifest \
		    2>/dev/null`
		[ $? = 0 ] || plmh=""
		plmn="etc_svc_profile_platform_${plm}_xml"
	else
		plmh=""
	fi

	[ -n "$gh" ] && {
		echo "Preserving generic hash ($gh)."
		/usr/sbin/svccfg -s smf/manifest addpg ${gn} framework
		/usr/sbin/svccfg -s smf/manifest setprop ${gn}/md5sum = \
		    opaque: $gh
		/usr/sbin/svccfg -s smf/manifest setprop ${gn}/manifestfile = \
		    astring: "/etc/svc/profile/generic.xml"
	}
	[ -n "$glh" ] && {
		echo "Preserving generic_limited hash ($glh)."
		/usr/sbin/svccfg -s smf/manifest addpg ${gln} framework
		/usr/sbin/svccfg -s smf/manifest setprop ${gln}/md5sum = \
		    opaque: $glh
		/usr/sbin/svccfg -s smf/manifest setprop ${gln}/manifestfile = \
		    astring: "/etc/svc/profile/generic.xml"
	}
	[ -n "$plh" ] && {
		echo "Preserving platform hash ($plh)."
		/usr/sbin/svccfg -s smf/manifest addpg $pln framework
		/usr/sbin/svccfg -s smf/manifest setprop $pln/md5sum = \
		    opaque: $plh
		/usr/sbin/svccfg -s smf/manifest setprop ${pln}/manifestfile = \
		    astring: "/etc/svc/profile/platform_${pl}_xml"
	}
	[ -n "$plmh" ] && {
		echo "Preserving platform hash ($plmh)."
		/usr/sbin/svccfg -s smf/manifest addpg $plmn framework
		/usr/sbin/svccfg -s smf/manifest setprop $plmn/md5sum = \
		    opaque: $plmh
		/usr/sbin/svccfg -s smf/manifest setprop \
		    ${plmn}/manifestfile = \
		    astring: "/etc/svc/profile/platform_${plm}_xml"
	}

	#
	# Move symlinks from /var/svc/profile to /etc/svc/profile
	#
	generic_prof="/var/svc/profile/generic.xml"
	ns_prof="/var/svc/profile/name_service.xml"
	inetd_prof="/var/svc/profile/inetd_services.xml"
	platform_prof="/var/svc/profile/platform.xml"
	[ -L "$generic_prof" ] && mv $generic_prof /etc/svc/profile/
	[ -L "$ns_prof" ] && mv $ns_prof /etc/svc/profile/
	[ -L "$inetd_prof" ] && mv $inetd_prof /etc/svc/profile/
	[ -L "$platform_prof" ] && mv $platform_prof /etc/svc/profile/

	return 0
}

#
# 2.  Manifest import.  Application directories first, then
# site-specific manifests.
#
import_manifests()
{
	local basedir=$1
	local console_print=$2
	local logf="/etc/svc/volatile/manifest_import.$$"

	rm -f $logf

	nonsite_dirs=`/usr/bin/find $basedir/* -name site \
	    -prune -o -type d -print -prune`

	if [ -n "$_MFST_DEBUG" ]; then
		nonsite_manifests=`/lib/svc/bin/mfstscan $nonsite_dirs`
		site_manifests=`/lib/svc/bin/mfstscan $basedir/site`

		manifests="$nonsite_manifests $site_manifests"

		echo "Changed manifests to import:"
		for m in $manifests; do echo "  $m"; done
	fi

	#
	# Upon boot, attempt to move the repository to tmpfs.
	#
	if [ -z "$ALT_REPOSITORY" ] && [ -z "$ALT_MFST_DIR" ]; then 
		/usr/sbin/svcadm _smf_repository_switch fast
	fi

	#
	# Import the manifests while giving a running display of imports on
	# console, and a final count in the logfile.
	#
	dirs="$nonsite_dirs"
	[ -d "$basedir/site" ] && dirs="$dirs $basedir/site"

	if [ "$console_print" = "true" ]; then
		$X /usr/sbin/svccfg import -p /dev/msglog $dirs > $logf 2>&1
	else
		$X /usr/sbin/svccfg import $dirs > $logf 2>&1
	fi

	grep "Loaded .*. smf(7) service descriptions" $logf > /dev/null 2>&1
	if [ $? -eq 0 ]; then
		activity=true
	fi

	if [ -s $logf ]; then
		grep "smf(7) service descriptions failed to load" $logf > /dev/null 2>&1
		failures=$?
		if [ $failures -eq 0 ]; then
			echo "svccfg warnings:"
		fi
		cat $logf

		if [ $failures -eq 0 ] && [ "$console_print" = "true" ]; then
			msg="svccfg import warnings.  See"
			msg="$msg /var/svc/log/system-manifest-import:default.log ."
			echo $msg > /dev/msglog
		fi
	fi
	rm -f $logf
}

#
# 3.  Profile application.  We must create the platform profile upon
# first boot, as we may be a diskless client of a platform or
# architecture distinct from our NFS server.
#
# Generic and platform profiles are only supported in /etc.
#
apply_profile()
{
	#
	# If smf/manifest doesn't have any profile under /etc/var/profile,
	# this is very likely an import after upgrade so call
	# preserve_system_profiles in that case.
	#
	LC_ALL=C pl=`/usr/bin/uname -i | /usr/bin/tr , _`
	pln="etc_svc_profile_platform_${pl}_xml"

	LC_ALL=C plm=`/usr/bin/uname -m | /usr/bin/tr , _`
	[ $plm != $pl ] && plmn="etc_svc_profile_platform_${plm}_xml"

	preserve_profiles=1
	for prof in $pln $plmn etc_svc_profile_platform_none_xml \
	    etc_svc_profile_generic_limited_net_xml \
	    etc_svc_profile_generic_open_xml; do
		if /usr/bin/svcprop -p $prof smf/manifest >/dev/null 2>&1
		then
			preserve_profiles=0
			break
		fi
	done

	if [ $preserve_profiles -eq 1 ]; then
		echo "/etc/svc system profiles not found: upgrade system profiles"
		preserve_system_profiles || return
	fi

	local prefix="/etc/svc/profile"
	svccfg_apply $prefix/generic.xml
	if [ ! -f $prefix/platform.xml ]; then
		this_karch=`uname -m`
		this_plat=`uname -i`

		if [ -f $prefix/platform_$this_plat.xml ]; then
			platform_profile=platform_$this_plat.xml
		elif [ -f $prefix/platform_$this_karch.xml ]; then
			platform_profile=platform_$this_karch.xml
		else
			platform_profile=platform_none.xml
		fi

		ln -s $platform_profile $prefix/platform.xml
	fi

	svccfg_apply $prefix/platform.xml
}

#
# 4.  Upgrade handling.  The upgrade file generally consists of a series
# of svcadm(8) and svccfg(8) commands.
#
handle_upgrade()
{

	[ -f /var/svc/profile/upgrade ] && activity=true

	(
		unset SVCCFG_CHECKHASH

		if [ -f /var/svc/profile/upgrade ]; then
			. /var/svc/profile/upgrade

			/usr/bin/mv /var/svc/profile/upgrade \
			    /var/svc/profile/upgrade.app.`date +\%Y\%m\%d\%H\%M\%S`
		fi

		#
		# Rename the datalink upgrade script file. This script is used in the
		# network/physical service to upgrade datalink configuration, but
		# the file cannot be renamed until now (when the file system becomes
		# read-write).
		#
		datalink_script=/var/svc/profile/upgrade_datalink
		if [ -f "${datalink_script}" ]; then
			/usr/bin/mv "${datalink_script}" \
			    "${datalink_script}".app.`date +\%Y\%m\%d\%H\%M\%S`
		fi
	)
}

#
# 5.  Giving administrator the final say, apply site.xml profile and profiles
#     under /etc/svc/profile/site directory.
#
apply_site_profile()
{
        local prefix="$1"
	[ -f $prefix/site.xml ] && svccfg_apply $prefix/site.xml

	if [ -d $PROFILE_DIR_SITE ] && [ "$1" = "/etc/svc/profile" ]; then
		svccfg_apply $PROFILE_DIR_SITE
	fi
}

[ "$early" = "true" ] && [ -f /etc/.LIVEBOOT_MODE ] && exit 0

#
# 0b Cleanup deathrow
#
if [ "$early" = "false" ];then
	deathrow=/etc/svc/deathrow
	if [ -s $deathrow ];then
		#
		# svc.startd has unconfigured the services found in deathrow,
		# clean them now.
		#
		while read fmri mfst pkgname; do
			# Delete services and instances from the deathrow file.
			/usr/sbin/svccfg delete -f $fmri >/dev/null 2>&1
			# Remove deathrow manifest hash.
			/usr/sbin/svccfg delhash -d $mfst >/dev/null 2>&1
		done < $deathrow
		/usr/bin/mv $deathrow $deathrow.old
	fi
fi

SVCCFG_CHECKHASH=1 export SVCCFG_CHECKHASH

#
# 0c Clean up repository
#
if [ "$early" = "false" ]; then
	if [ -z "$X" ] && /usr/bin/svcprop smf/manifest 2>/dev/null |
	    /usr/bin/grep '^ar_svc_[^/]*/md5sum opaque ' >/dev/null
	then
		set -- `
			/usr/bin/svcprop smf/manifest 2>/dev/null |
			    /usr/bin/grep '^ar_svc[^/]*/md5sum opaque ' |
			    /usr/bin/tr '/' ' ' |
			    while read pg prop type value; do
				echo "$pg/$value"
			done
		`
		backup=`echo "$#/$#" | sed 's/.//g'`
		fwidth=`echo -e "$#\c" | wc -c`

		echo -e "Converting obsolete repository entries: \c" > /dev/msglog
		i=1; n=$#
		while [ $# -gt 0 ]; do
			printf "%${fwidth}s/%${fwidth}s" $i $n > /dev/msglog
			echo $1 | sed 's:/: :' | (
				read pg value

				(echo "select /smf/manifest"; echo "delpg v$pg") |
				    /usr/sbin/svccfg 2>/dev/null >/dev/null
				(echo "select /smf/manifest"; echo "delpg $pg") |
				    /usr/sbin/svccfg 2>/dev/null >/dev/null
				(echo "select /smf/manifest";
				    echo "addpg v$pg framework") |
				    /usr/sbin/svccfg 2>/dev/null >/dev/null
				(echo "select /smf/manifest";
				    echo "setprop v$pg/md5sum = opaque: $value") |
				    /usr/sbin/svccfg 2>/dev/null >/dev/null
			)
			i=`expr $i + 1`
			shift
			echo -e "$backup\c" > /dev/msglog
		done
		echo > /dev/msglog
		echo "Converted $n obsolete repository entries"
		activity=true
	fi

fi

#
# If the alternate repository and directory are specified, simply set
# SVCCFG_REPOSITORY env, run svccfg import on the given directory, and
# exit.
#
if [ -n "$ALT_REPOSITORY" ] && [ -n "$ALT_MFST_DIR" ]; then 
	SVCCFG_REPOSITORY=$ALT_REPOSITORY export SVCCFG_REPOSITORY
	import_manifests "$ALT_MFST_DIR" false
	unset SVCCFG_REPOSITORY
	exit 0
fi

#
# Call import and apply profiles here
#
if [ "$early" = "true" ]; then
	import_manifests "/lib/svc/manifest" true
	apply_profile
	apply_site_profile "/etc/svc/profile"
else
	#
	# Process both /lib/svc/manifest and /var/svc/manifest
	# during late manifest-import
	#
	# First import the manifests
	#
	import_manifests "/lib/svc/manifest" true
	import_manifests "/var/svc/manifest" true
	test -d /ec/var/svc/manifest \
		&& import_manifests "/ec/var/svc/manifest" true
	test -d /opt/local/var/svc/manifest \
		&& import_manifests "/opt/local/var/svc/manifest" true

	#
	# Apply profiles
	#
	apply_profile
	apply_site_profile "/etc/svc/profile"

	#
	# Run the upgrade script
	#
	handle_upgrade
	apply_site_profile "/var/svc/profile"
fi


#
# 6.  Final actions.
#

if $activity; then
	/usr/sbin/svcadm _smf_backup "manifest_import" || true
fi

#
# If the filesystem is NOT read only then move the repo back to perm
# There is no care wether the switch was made or not, but just want
# to move it.  If it is already perm this does not affect anything
# at least on the surface.  REALLY want to improve on this...
#
touch /etc/svc/smf_rwtest.$$ > /dev/null 2>&1
if [ $? -eq 0 ]; then
	rm -f /etc/svc/smf_rwtest.$$
	/usr/sbin/svcadm _smf_repository_switch perm || { \
	    echo -e "Repository switch back operation failed, \c"
	    echo "please check the system log for the"
	    echo "possible fatal error messages."
	    exit $SMF_EXIT_ERR_FATAL
	    }
fi

if $activity; then
	/usr/sbin/svccfg cleanup | /usr/bin/tee /dev/msglog
else
	cleanup_needwork
	if [ $? -ne 0 ]; then
		/usr/sbin/svccfg cleanup -a | /usr/bin/tee /dev/msglog
	fi
fi

if [ ! -f /etc/motd ]; then
	test -f /usr/share/base-files/motd && /usr/bin/cp -f /usr/share/base-files/motd /etc/motd
fi

exit 0
